package com.tarena.lbs.marketing.web.service;

import com.alibaba.nacos.common.utils.CollectionUtils;
import com.github.pagehelper.PageInfo;
import com.tarena.lbs.attach.api.AttachApi;
import com.tarena.lbs.base.common.utils.Asserts;
import com.tarena.lbs.base.protocol.exception.BusinessException;
import com.tarena.lbs.base.protocol.pager.PageResult;
import com.tarena.lbs.basic.api.UserGroupApi;
import com.tarena.lbs.common.passport.principle.UserPrinciple;
import com.tarena.lbs.common.security.utils.LbsSecurityContenxt;
import com.tarena.lbs.marketing.web.repository.ActivityRepository;
import com.tarena.lbs.marketing.web.repository.CouponCodeRepository;
import com.tarena.lbs.marketing.web.repository.CouponRepository;
import com.tarena.lbs.marketing.web.repository.UserCouponsRepository;
import com.tarena.lbs.pojo.attach.dto.AttachQrDTO;
import com.tarena.lbs.pojo.attach.param.AttachQRParam;
import com.tarena.lbs.pojo.basic.event.LocationStoreEvent;
import com.tarena.lbs.pojo.marketing.param.UserCouponsParam;
import com.tarena.lbs.pojo.marketing.po.ActivityPO;
import com.tarena.lbs.pojo.marketing.po.CouponCodePO;
import com.tarena.lbs.pojo.marketing.po.CouponPO;
import com.tarena.lbs.pojo.marketing.po.UserCouponsPO;
import com.tarena.lbs.pojo.marketing.query.UserCouponCodeQuery;
import com.tarena.lbs.pojo.marketing.query.UserCouponQuery;
import com.tarena.lbs.pojo.marketing.vo.UserCouponsVO;
import com.tarena.lbs.stock.api.StockApi;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class CouponUserService {
    @Autowired
    private ActivityRepository activityRepository;
    @Autowired
    private CouponRepository couponRepository;
    @Autowired
    private UserCouponsRepository userCouponsRepository;
    @Autowired
    private CouponCodeRepository couponCodeRepository;
    @DubboReference
    private UserGroupApi userGroupApi;
    @DubboReference
    private StockApi stockApi;
    @Autowired
    private RedisTemplate redisTemplate;

    public void autoSendCoupon(LocationStoreEvent event) throws BusinessException {
        //select * from activity where
        //FIND_IN_SET(137,shop_ids) or
        //FIND_IN_SET(139,shop_ids) or
        //FIND_IN_SET(140,shop_ids)
        //1.下发的优惠券都来自于店铺的活动 使用List<Integer> storeIds 查询所有活动的列表
        List<ActivityPO> activityPos=activityRepository.getActivitiesByStoreIds(event.getStoreIds());
        log.info("本次消费找到活动:{}",activityPos);
        //多个活动 给一个用户领优惠券
        //用户主动领取优惠券 看到一个活动
        //2.校验活动 不仅能校验多个活动 还能校验一个活动
        List<ActivityPO> legalActivities = checkActivities(activityPos);
        Asserts.isTrue(CollectionUtils.isEmpty(legalActivities),new BusinessException("-2","没有合法活动"));
        log.info("本次消费合法活动:{}",legalActivities);
        //3.利用userId 和 活动的目标人群校验所属人群是否合法
        List<ActivityPO> availableActivities=checkUserGroupActivity(event.getUserId(),legalActivities);//利用userId和每个活动中的targetConsumer 校验用户是否属于该人群
        Asserts.isTrue(CollectionUtils.isEmpty(availableActivities),new BusinessException("-2","没有目标人群活动"));
        log.info("本次消费符合人群目标的活动:{}",activityPos);
        //4.利用活动查询优惠券 利用剩余的可用活动 查询绑定的优惠券
        List<CouponPO> couponPos=checkExistsCoupons(availableActivities);
        //5.校验优惠券 判断null 判断合法 非空优惠券
        Asserts.isTrue(CollectionUtils.isEmpty(couponPos),new BusinessException("-2","没有优惠券"));
        log.info("本次消费活动下存在的优惠券:{}",couponPos);
        //6.查询库存剩余 留下剩余>0的库存的对应的优惠券 read
        List<CouponPO> stockCouponPos=checkCouponStock(couponPos);
        Asserts.isTrue(CollectionUtils.isEmpty(stockCouponPos),new BusinessException("-2","优惠券都没库存了"));
        //7.将该用户 领取没达到优惠券上限的筛选出来
        List<CouponPO> availableCoupons=checkUsageLimit(stockCouponPos,event.getUserId());
        Asserts.isTrue(CollectionUtils.isEmpty(availableCoupons),new BusinessException("-2","没有可领取的优惠券"));
        receiveCoupons(stockCouponPos,event.getUserId());
    }

    private List<CouponPO> checkUsageLimit(List<CouponPO> stockCouponPos, Integer userId) {
        //判断当前用户 对应coupon 是否已经领取到达上限了.把没有到达上限的筛选出来返回
        return stockCouponPos.stream().filter(po->this.reachLimit(po,userId)).collect(Collectors.toList());
    }
    private boolean reachLimit(CouponPO po, Integer userId){
        //每领取一次优惠券码,用户都会在coupon_user记录一次领取记录 哪个优惠券couponId 哪个用户userId
        Integer couponId=po.getId();
        Long total=userCouponsRepository.countUserCoupons(couponId,userId);
        return total<po.getUsageLimit();
    }
    private void receiveCoupons(List<CouponPO> availableCoupons, Integer userId) {
        for (CouponPO availableCoupon : availableCoupons) {
            try{
                receiveCoupon(availableCoupon,userId);
            }catch (BusinessException e){
                log.error("当前优惠券:{},被用户:{},领取失败",availableCoupon,userId);
            }
        }
    }
    @Transactional(rollbackFor = Exception.class)
    public void receiveCoupon(CouponPO couponPO,Integer userId) throws BusinessException {
        //8.读取优惠券可用券码 从当前优惠券的券码表拿到一张未领取状态的券码数据
        CouponCodePO couponCodePo=couponCodeRepository.getAvailableCode(couponPO.getId());
        Asserts.isTrue(couponCodePo==null,new BusinessException("-2","优惠券券码不足"));
        log.info("拿到可用券码:{}",couponCodePo);
        //9.存储用户领取记录 write 封装用户领取记录的数据写到 coupon_user表格里
        UserCouponsPO poParam=new UserCouponsPO();
        BeanUtils.copyProperties(couponPO,poParam);
        poParam.setId(null);
        poParam.setUserId(userId);
        poParam.setCouponId(couponPO.getId());
        poParam.setCouponCode(couponCodePo.getCouponCode());
        poParam.setStatus(0);
        poParam.setCreateAt(new Date());
        poParam.setUpdateAt(poParam.getCreateAt());//渠道 活动id
        poParam.setCouponValue(couponPO.getDiscountValue());
        poParam.setReceiveChannel(1);//自动下发1 手动领取2 消息推送3
        poParam.setActivityId(0);//无效数据,领取记录想要知道活动完全可以使用couponId
        poParam.setShopId(0);//无效数据 保证新增成功
        log.info("领取优惠券的记录po:{}",poParam);
        userCouponsRepository.saveUserCoupons(poParam);
        //10.修改优惠券码的状态 write 从0改成1 未领取->已领取
        couponCodeRepository.updateCouponCodeStatus(couponCodePo.getId(),1);
        //11.减少库存 高并发争抢最后一个优惠券库存 可能导致减库存失败 write
        //远程调用 减库存 对该优惠券减库存的结果 如果失败,我们自己保证当前的业务回滚
        boolean result = stockApi.reduceStock(couponPO.getId());
        //活动 查询优惠券 每一步校验 单独封装方法 以便于后续有可能复用.
        //如果减库存失败 手动抛出异常 让当前一个事务中 上面 记录领取数据 更新券码状态sql回滚
        if(!result){
            throw new BusinessException("-2","优惠券券码不足");
        }
    }

    private List<CouponPO> checkCouponStock(List<CouponPO> couponPos) {
        //筛选出来 库存大于0的优惠券
        return couponPos.stream().filter(po->stockEnough(po)).collect(Collectors.toList());
    }
    private boolean stockEnough(CouponPO po){
        return stockApi.checkEnoughStock(po.getId());
    }

    private List<CouponPO> checkExistsCoupons(List<ActivityPO> availableActivities) {
        //复用单活动 对单优惠券查询 做一个循环,将所有活动对应的非空优惠券作为返回值返回
        List<CouponPO> couponPos=availableActivities.stream().map(po->{
            return getCouponFromActivity(po);
        }).collect(Collectors.toList());
        return couponPos.stream().filter(po->po!=null).collect(Collectors.toList());
    }
    private CouponPO getCouponFromActivity(ActivityPO activityPO){
        //活动绑定的优惠券id
        Integer rewardCouponId = activityPO.getRewardCouponId();
        return couponRepository.getCouponById(rewardCouponId);
    }

    private List<ActivityPO> checkUserGroupActivity(Integer userId, List<ActivityPO> legalActivities) {
        /*List<ActivityPO> availableActivities = new ArrayList<>();
        for (ActivityPO legalActivity : legalActivities) {
            if (checkUserTarget(userId,Integer.valueOf(legalActivity.getTargetCustomer()))){
                availableActivities.add(legalActivity);
            }
        }*/
        return legalActivities.stream().filter(po-> this.checkUserTarget(userId,Integer.valueOf(po.getTargetCustomer())))
                .collect(Collectors.toList());
    }
    private boolean checkUserTarget(Integer userId,Integer userGroupId){
        return userGroupApi.isUserGroup(userId,userGroupId);
    }

    private List<ActivityPO> checkActivities(List<ActivityPO> activityPos) {
        //校验的规则 所有的活动 必须是 满足一下几个条件才算领取优惠券合法活动
        //满足的活动 返回 不满足条件的活动 删除过滤
        if (CollectionUtils.isNotEmpty(activityPos)){
            return activityPos.stream().filter(po-> this.checkActivity(po))
                    .collect(Collectors.toList());
        }else{
            return null;
        }
    }
    public boolean checkActivity(ActivityPO activityPO){
        boolean flag=false;//默认是 不合法 如果下面的所有的条件都判断成功,那么就合法
        if (activityPO==null){
            return flag;
        }
        Long now=System.currentTimeMillis();//当前系统时间
        if (!(activityPO.getStartDate().getTime()<now && activityPO.getEndDate().getTime()>now)){
            return flag;
        }
        if (!(activityPO.getRewardType()==1)){
            return flag;
        }
        if (!(activityPO.getStatus()==0)){
            return flag;
        }
        if (!(activityPO.getEnableStatus()==0)){
            return flag;
        }
        return true;
    }

    public PageResult<UserCouponsVO> userCouponsPage(UserCouponQuery query) throws BusinessException {
        UserPrinciple userPrinciple = LbsSecurityContenxt.getLoginToken();
        Asserts.isTrue(userPrinciple==null,new BusinessException("-2","用户认证失败"));
        query.setUserId(userPrinciple.getId());
        PageInfo<UserCouponsPO> poPageInfo=userCouponsRepository.userCouponsPage(query);
        PageResult<UserCouponsVO> voPages=new PageResult<>();
        voPages.setPageNo(poPageInfo.getPageNum());
        voPages.setPageSize(poPageInfo.getPageSize());
        voPages.setTotal(poPageInfo.getTotal());
        List<UserCouponsVO> vos=null;
        if (poPageInfo.getTotal()>0){
            vos=poPageInfo.getList().stream().map(po->{
                UserCouponsVO vo=new UserCouponsVO();
                BeanUtils.copyProperties(po,vo);
                return vo;
            }).collect(Collectors.toList());
        }
        voPages.setObjects(vos);
        return voPages;
    }

    public UserCouponsVO userCouponsDetail(UserCouponCodeQuery query) throws BusinessException {
        //1.查询缓存 查询这个用户领取的某一个优惠券 构造一个该数据缓存的唯一key
        //"业务前缀"描述这个数据属于什么业务+"业务数据" 唯一值 id 编码 等等
        String userCouponsKey="user:coupons:code:"+query.getCouponCode();
        Boolean result = redisTemplate.hasKey(userCouponsKey);//不会读取数据 只会读key值
        ValueOperations opsForValue = redisTemplate.opsForValue();
        if (result){
            log.info("查询条件code:{},命中了缓存");
            //1.1如果命中,直接返回结果 string的redis数据结构 存储的key-value
            return (UserCouponsVO) opsForValue.get(userCouponsKey);
        }else{
            //1.2没有命中,查询数据层 调用图片生成二维码图片url
            log.info("查询条件code:{},没有命中了缓存");
            //2.查询po数据 根据优惠券码 创建一个二维码的图片 拿到url分装给vo
            //2.1 仓储层 利用code编码查询数据
            UserCouponsPO po=userCouponsRepository.getUserCouponsDetail(query.getCouponCode());
            Asserts.isTrue(po==null,new BusinessException("-2","券码不存在"));
            UserCouponsVO vo=new UserCouponsVO();
            //2.2 拷贝属性
            BeanUtils.copyProperties(po,vo);
            //2.3 远程调用封装 优惠券二维码 url地址
            generateCouponCodeQr(vo);
            //3. 缓存补充vo
            opsForValue.set(userCouponsKey,vo);
            //4. 返回vo对象
            return vo;
        }


    }
    @DubboReference
    private AttachApi attachApi;
    private void generateCouponCodeQr(UserCouponsVO vo) {
        AttachQRParam attachQRParam=new AttachQRParam();
        attachQRParam.setBusinessId(vo.getId());
        attachQRParam.setBusinessType(600);
        //希望扫描二维码的终端 可以发起一个我们能控制的请求
        //一般二维码会携带url地址 比如京东
        attachQRParam.setContent("https://www.baidu.com?couponCode="+vo.getCouponCode());
        AttachQrDTO attachQrDTO = attachApi.generateQrCode(attachQRParam);
        if (attachQrDTO!=null){
            vo.setCouponUrl(attachQrDTO.getUrl());
        }
    }

    public void userCouponsSave(UserCouponsParam param) throws BusinessException {
        //1.解析登录用户
        UserPrinciple userPrinciple = LbsSecurityContenxt.getLoginToken();
        Asserts.isTrue(userPrinciple==null,new BusinessException("-2","用户认证失败"));
        param.setUserId(userPrinciple.getId());
        //2.查询活动.校验活动是否合法 是否存在
        ActivityPO activityPO = activityRepository.getActivityById(param.getActivityId());
        boolean result = checkActivity(activityPO);
        Asserts.isTrue(!result,new BusinessException("-2","活动不合法"));
        //3.验证活动目标人群是否符合要求
        result=checkUserTarget(param.getUserId(),Integer.valueOf(activityPO.getTargetCustomer()));
        Asserts.isTrue(!result,new BusinessException("-2","人群不符合要求"));
        //4.验证优惠券是否存在
        List<ActivityPO> availableActivities=new ArrayList<>();
        availableActivities.add(activityPO);
        List<CouponPO> couponPOS = checkExistsCoupons(availableActivities);
        Asserts.isTrue(CollectionUtils.isEmpty(couponPOS),new BusinessException("-2","优惠券不存在"));
        //5.查询剩余库存充足的,要么1个元素 要么没元素
        List<CouponPO> enoughCoupons = checkCouponStock(couponPOS);
        Asserts.isTrue(CollectionUtils.isEmpty(enoughCoupons),new BusinessException("-2","当前优惠券库存不足"));
        //6.将该用户 领取没达到优惠券上限的筛选出来
        List<CouponPO> availableCoupons=checkUsageLimit(enoughCoupons,userPrinciple.getId());
        Asserts.isTrue(CollectionUtils.isEmpty(availableCoupons),new BusinessException("-2","没有可领取的优惠券"));
        receiveCoupons(availableCoupons,userPrinciple.getId());
    }
}
